package redis

import (
	"errors"
	"math"
	"strconv"
	"time"

	"github.com/go-redis/redis"
)

/**
本项目使用简化版的投票分数
投一票加432分 86400/200 --> 200张赞成票就让某个帖子续一天

投票的几种情况
direction = 1
1.之前没投过票，现在投赞成票 --> 更新分数和创建投票记录 差值绝对值 1 分数+432
2.之前投反对票，现在改投赞成票 --> 更新分数和投票记录 差值绝对值 2 分数+432*2

direction = 0
1.之前投赞成票，现在取消投票 差值绝对值 1 分数-432
2.之前投反对票，现在取消投票 差值绝对值 1 分数+432

direction = -1
1.之前没投过票，现在投反对票 -->更新分数和创建投票记录 差值绝对值 1 分数-432
2.之前投赞成票，现在投反对票 差值绝对值 2 分数-432*2

投票限制：
每个帖子自发表之日起一周之内允许用户投票，超过一周就不允许再投票
	1.帖子到期之后将redis中保存的赞成票数及反对票数 存储到mysql中
	2.帖子到期之后 删除keyPostVotedZSetPF
*/
const (
	//一周的秒数
	oneWeekInSeconds = 7 * 24 * 3600
	scorePerVote     = 432 //每一票值多少分
)

var (
	ErrVoteTimeExpire = errors.New("投票时间已过")
	ErrVoteRepested   = errors.New("不允许重复投票")
)

/**将创建的帖子 存入redis key:value -> time:postId
并初始化 该帖子的分数 存入redis key:value -> score:postId
*/
func CreatePost(postId uint64, communityId int64) error {
	//两个redis操作 要么都成功，要么都失败 ，使用redis 事务 pipeline
	pipeline := rdb.TxPipeline()
	//帖子时间
	pipeline.ZAdd(getRedisKey(keyPostTimeZSet), redis.Z{
		Score:  float64(time.Now().Unix()),
		Member: postId,
	})
	//帖子分数
	pipeline.ZAdd(getRedisKey(keyPostScoreZSet), redis.Z{
		Score:  float64(time.Now().Unix()),
		Member: postId,
	})
	//把帖子id 加到社区set中
	cKey := getRedisKey(KeyCommunitySetPF + strconv.Itoa(int(communityId)))
	pipeline.SAdd(cKey, postId)
	_, err := pipeline.Exec()
	return err
}

func VoteForPost(userId, postId string, value float64) error {
	//1.判断投票限制
	//去redis取帖子发布时间
	postTime := rdb.ZScore(getRedisKey(keyPostTimeZSet), postId).Val()
	//当前时间 - 发帖时间 > 一周 则该帖子 不允许用户投票 返回报错
	if float64(time.Now().Unix())-postTime > oneWeekInSeconds {
		return ErrVoteTimeExpire
	}

	//2.更新帖子分数
	//先查看当前用户给当前帖子的投票记录
	/**
	没给该帖子投过票 则查不到记录 返回0
	赞成票 1
	反对票 -1
	*/
	ov := rdb.ZScore(getRedisKey(keyPostVotedZSetPF+postId), userId).Val()

	//如果这次投票的值 和 redis存储的值一致，就提示 不允许重复投票
	if value == ov {
		return ErrVoteRepested
	}

	var op float64
	//op 表示 用户本次投票 对应帖子分数的改变 的正负号 op = -1 表示 帖子分数介绍 op = 1 表示帖子分数增加
	if value > ov {
		op = 1
	} else {
		op = -1
	}
	diff := math.Abs(ov - value) //计算两次投票的差值

	//2的部分操作和3的操作需要放到一个事务中
	pipeline := rdb.TxPipeline()

	//更新 帖子分数
	pipeline.ZIncrBy(getRedisKey(keyPostScoreZSet), op*diff*scorePerVote, postId)
	//3.记录用户为该帖子投票的数据
	/**
	value == 0 表示用户取消投票 从redis中移除 键值对
	value != 0 表示用户有投票行为 让redis中添加记录（如果记录已存在 则更新）
	*/
	if value == 0 {
		pipeline.ZRem(getRedisKey(keyPostVotedZSetPF+postId), postId)
	} else {
		pipeline.ZAdd(getRedisKey(keyPostVotedZSetPF+postId), redis.Z{
			Score:  value, //赞成票还是反对票
			Member: userId,
		})
	}
	_, err := pipeline.Exec()
	return err
}
